package seiacpjava_a2;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Scanner;

import org.github.jamm.MemoryMeter;

import seiacpjava_a2.datastructures.DataStructure;
import seiacpjava_a2.datastructures.DataStructureFactory;
import seiacpjava_a2.utilities.FileParser;
import seiacpjava_a2.utilities.NumberOfAccessCalculator;

public class Main {
	static final String EOL = "\r\n";
	static final String MODULO_STRATEGY = "modulo";
	static final String REFLEXIVE_STRATEGY = "reflexive";
	
	static String dataStructureName;
	static String inputFile;
	static String outputFile;
	static String strategy;
	static String outputText = "";
	static int[] items = null;
	static DataStructure dataStructure = null;
	static long fileParsingExecutionTime;
	static long fileWritingExecutionTime;
	static long dataStructureCreationTime;
	static long memoryUsed;
	
	
	public static void main(String[] args) {

		if (!isArgumentInputNumberValid(args)) {
			System.out.println("Usage: <data_structure> <input_file> <output_file> [<hashing_strategy>]");
			return;
		}
		
		dataStructureName = args[0];
		inputFile = args[1];
		outputFile = args[2];
		
		if ( args.length > 3)
		{
		strategy = args[3];
		}
		
		StringBuilder output = new StringBuilder();
		NumberOfAccessCalculator numberOfAccessCalculator = new NumberOfAccessCalculator();
		MemoryMeter memoryMeter = new MemoryMeter();
		try {
			
			
			
			Path inputFilePath = FileSystems.getDefault().getPath(inputFile);
			FileParser parser = new FileParser(inputFilePath);
			long parsingStart = System.nanoTime();
			items = parser.getNumbers();
			long parsingEnd = System.nanoTime();
			
			long buildingStart;
			long buildingEnd;
			
			if ( dataStructureName.equals(DataStructureFactory.BIN_SRCH_TREE_RECURSIVE)){
				  dataStructure = DataStructureFactory.createByName(dataStructureName);
				  buildingStart = System.nanoTime();
				  buildDataStructure(items, dataStructure);
				  buildingEnd = System.nanoTime();
			}else{
				
				if (MODULO_STRATEGY.equals(strategy)){
				    buildingStart = System.nanoTime();
				    dataStructure = DataStructureFactory.createHashTableWithModuloStrategy(items);
				    buildingEnd = System.nanoTime();
				}else{
					 buildingStart = System.nanoTime();
					 dataStructure = DataStructureFactory.createHashTableWithReflexiveStrategy(items);
					 buildingEnd = System.nanoTime();
				}
			}
			
			
			memoryUsed = memoryMeter.measureDeep(dataStructure);
			fileParsingExecutionTime = parsingEnd - parsingStart;
			dataStructureCreationTime = buildingEnd - buildingStart;
		} catch (IllegalArgumentException e) {
			displayDataStructureFactoryCreationError();
			return;
		} catch (IOException ex) {
			System.out.println(String.format("There was an error parsing the file %s", inputFile));
			return;
			
		} 
		displayHelpWelcomeHeader();
		try (Scanner sc = new Scanner(System.in)) {
			while (sc.hasNext()) {
				String input = sc.nextLine();
				if (shouldTerminateProgram(input)) 
					break;
				try {
					int value = Integer.valueOf(input);
					
					boolean found = dataStructure.contains(value);
					numberOfAccessCalculator.addNbrAccess(dataStructure.getNumberOfAccesses(), found);
					String result = String.format("Value %d was %s with a number of access of %d", value,formatBooleanToString(found),dataStructure.getNumberOfAccesses());
					System.out.println(result);
					System.out.println(String.format("The average Number of Accesses for found items is %f", numberOfAccessCalculator.calculateFoundAverageNbrAccesses()));
					System.out.println(String.format("The average Number of Accesses for items not found is %f", numberOfAccessCalculator.calculateNotFoundAverageNbrAccesses()));
					output.append(result + EOL);
				} catch (NumberFormatException e) {
					System.out.println("please enter a valid integer!");
				}
			}
			output.append(generateOutputStatistics(numberOfAccessCalculator));
			writeToFile(output.toString(),outputFile,false);
			System.out.println("finished reading results, the ouput file " + outputFile + " was generated...");
		}
	}
	
	private static boolean isArgumentInputNumberValid(String[] args) {
		return args.length >= 3;
	}
	
	private static void buildDataStructure(int[] items,DataStructure dataStructure) {
		for (int item : items) 
			dataStructure.add(item);
	}
	
	private static void displayDataStructureFactoryCreationError() {
		System.out.println(String.format("The dataStructure argument input name is invalid please use %s or %s",
				  DataStructureFactory.HASHTABLE,
				  DataStructureFactory.BIN_SRCH_TREE_RECURSIVE));
	}
	
	private static void displayHelpWelcomeHeader() {
		System.out.println("COM411 Assignment 2 Project");
		System.out.println("	-To search for an item enter the value and press enter");
		System.out.println("	-To exit type 'exit', 'end' or use the key combination 'CTL+Z'");
	}
	
	private static String generateOutputStatistics(NumberOfAccessCalculator nbrAccessCalculator) {
		StringBuilder builder = new StringBuilder();
		
		builder.append("==================================================================================" + EOL);
		builder.append( "File Parsing Time (Nano Seconds) : " + fileParsingExecutionTime + EOL);
		builder.append( "DataStructure Name : " + dataStructure.getDatatStructureName() + EOL);		
		builder.append( "DataStructure Creation Time (Nano Seconds) : " + dataStructureCreationTime + EOL);
		builder.append( "File Writing Time (Nano Seconds) : " + fileWritingExecutionTime + EOL);
		builder.append( "Memory used (Bytes) : " + memoryUsed + EOL);
		builder.append(String.format("Number of missed searches is %d "+ EOL,nbrAccessCalculator.nbrOfMissedSearches() ));
		builder.append(String.format("Number of found searches is %d "+ EOL,nbrAccessCalculator.nbrOfFoundSearches() ));
		builder.append(String.format("Average Number of accesses for missed searches is %f " + EOL,nbrAccessCalculator.calculateNotFoundAverageNbrAccesses() ));
		builder.append(String.format("Average Number of accesses for searches found is %f " +EOL,nbrAccessCalculator.calculateFoundAverageNbrAccesses()));
		
//		String usedMemory = "Memory Used (Bytes) : " + memoryStatistics.getUsedMemoryRunningAvg();
		return builder.toString();
	}
	
	
	private static String formatBooleanToString(boolean found) {
		return (found)? "found" : "not found";
	}
	
	private static boolean shouldTerminateProgram(String input) {
		return input!=null && (input.endsWith("exit") || input.endsWith("end") || input.endsWith("Ctr+Z"));
	}
	
	private static boolean shouldClearConsole(String input) {
		return input !=null && input.equals("cls");
	}

	private static void writeToFile(String output, String filePath, boolean isAppendModeOn) {
		try{
			  // Create file 
			  FileWriter fstream = new FileWriter(filePath,isAppendModeOn);
			  BufferedWriter out = new BufferedWriter(fstream);
			  out.write(output);
			  //Close the output stream
			  out.close();
		 }catch (Exception e){//Catch exception if any
			  System.err.println("Error: " + e.getMessage());
		 }
	}
	
}
